package base.audio;

import java.io.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.sound.sampled.*;

import base.LinkedBlockingQueue;

public class AudioManager {

	AtomicBoolean stopCapture = new AtomicBoolean(false);
	AudioFormat audioFormat;
	TargetDataLine targetDataLine;
	AudioInputStream audioInputStream;
	SourceDataLine sourceDataLine;
	boolean primeiro = true;
	AudioSocket socket;
	BlockingQueue<AudioPacket> filaInput;
	Jitter jitter;

	public static long bytes2millis(long bytes, AudioFormat format) {
		return (long) (bytes / format.getFrameRate() * 1000 / format.getFrameSize());
	}

	public static long millis2bytes(long ms, AudioFormat format) {
		return (long) (ms * format.getFrameRate() / 1000 * format.getFrameSize());
	}

	public AudioManager(AudioSocket socket) {
		try {
			this.socket = socket;
			filaInput = new LinkedBlockingQueue<AudioPacket>();
			jitter = new Jitter();

			audioFormat = getAudioFormat();

			DataLine.Info targetDataLineInfo = new DataLine.Info(TargetDataLine.class, audioFormat);
			targetDataLine = (TargetDataLine) AudioSystem.getLine(targetDataLineInfo);
			targetDataLine.open(audioFormat);
			targetDataLine.start();

			DataLine.Info sourceDataLineInfo = new DataLine.Info(SourceDataLine.class, audioFormat);
			sourceDataLine = (SourceDataLine) AudioSystem.getLine(sourceDataLineInfo);
			sourceDataLine.open(audioFormat);
			sourceDataLine.start();

		} catch (Exception e) {
			System.out.println(e);
			System.exit(0);
		}

	}

	public void stop() {
		stopCapture.set(true);
	}

	public void start() {
		Thread captureThread = new CaptureThread();
		captureThread.start();

		Thread receptorThread = new ReceptorThread();
		receptorThread.start();

		Thread playThread = new PlayThread();
		playThread.start();

	}

	private AudioFormat getAudioFormat() {
		float sampleRate = 8000.0F;
		// 8000,11025,16000,22050,44100
		int sampleSizeInBits = 8;
		// 8,16
		int channels = 1;
		// 1,2
		boolean signed = true;
		// true,false
		boolean bigEndian = false;
		// true,false
		return new AudioFormat(sampleRate, sampleSizeInBits, channels, signed, bigEndian);
		// return new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, sampleRate,
		// sampleSizeInBits, channels, channels * 2, sampleRate, bigEndian);
	}

	// =============================================//

	class CaptureThread extends Thread {
		// An arbitrary-size temporary holding buffer

		int tamanhoPacote = (int) AudioManager.millis2bytes(5, audioFormat);
		byte tempBuffer[] = new byte[tamanhoPacote];
		long timeStamp = 0;

		@Override
		public void run() {
			System.out.println(tamanhoPacote);
			stopCapture.set(false);
			try {
				while (!stopCapture.get()) {
					int cnt = targetDataLine.read(tempBuffer, 0, tamanhoPacote);
					if (cnt > 0) {
						byte[] data = tempBuffer;
						if(cnt < tamanhoPacote){
							data = new byte[cnt];
							System.arraycopy(tempBuffer, 0, data, 0, cnt);
						}
						if(timeStamp ==  0)timeStamp = System.currentTimeMillis();
						AudioPacket pkt = new AudioPacket(timeStamp, data);
						socket.sendPacket(pkt);
						timeStamp += AudioManager.bytes2millis(tamanhoPacote, audioFormat);
					}
				}
			} catch (Exception e) {
				System.out.println(e);
				System.exit(0);
			}
		}
	}

	class ReceptorThread extends Thread {
		@Override
		public void run() {
			long ultimoTimeStamp = 0;
			
			while (!stopCapture.get()) {
				try {
					AudioPacket pkt = socket.receivePacket();
					long timeStamp = pkt.getTimestamp(); // captura o tempo q o
															// pacote foi gerado
															// no emissor
					if (ultimoTimeStamp == 0) {
						ultimoTimeStamp = timeStamp;
					}
					if (timeStamp >= ultimoTimeStamp) {
						long tempoExecucao = jitter.getTimeoutInterval(timeStamp);
						pkt.setTempoExecucao(tempoExecucao);
						ultimoTimeStamp = timeStamp;
						filaInput.put(pkt);
					}
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}

			}
		}
	}

	class PlayThread extends Thread {

		@Override
		public void run() {
			try {

				while (!stopCapture.get()) {
					AudioPacket pacote = filaInput.take();
					byte audioData[] = pacote.getData();
					//if (System.currentTimeMillis() <= pacote.getTempoExecucao()) {

						/*while (System.currentTimeMillis() < pacote.getTempoExecucao()) {
							Thread.sleep(1);
						}*/

						sourceDataLine.write(audioData, 0, audioData.length);
						//sourceDataLine.drain();

					}
				//}
				// Block and wait for internal buffer of the
				// data line to empty.
				// sourceDataLine.drain();
				// sourceDataLine.close();
			} catch (Exception e) {
				System.out.println(e);
				System.exit(0);
			}
		}
	}
	// =============================================//

}